package org.gwu.seuss.service;

import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;

import javax.crypto.SecretKey;

import org.apache.commons.codec.binary.Base64;
import org.gwu.seuss.communication.crypto.CryptoUtil;
import org.gwu.seuss.communication.exception.AuthenticationException;
import org.gwu.seuss.communication.exception.AuthorizationException;
import org.gwu.seuss.communication.request.Actions;
import org.gwu.seuss.domain.User;
import org.gwu.seuss.persistence.UsersMapper;
import org.springframework.util.Assert;

public class UserServiceImpl extends ServiceBase implements UserService {

	private SecureRandom random = new SecureRandom();

	private UserServiceImpl() {

	}
	
	public UserServiceImpl (UsersMapper usersMapper) {
		this.usersMapper = usersMapper;
	}

	@Override
	public void login(String username, String password)
			throws AuthenticationException {
		Assert.hasText(username, "Username is not provided.");
		Assert.hasText(password, "Password is not provided.");

		User user = this.usersMapper.selectUserByEmail(username);
		if (user == null) {
			throw new AuthenticationException("User does not exist.");
		}
		if (user.isDisabled()) {
			throw new AuthenticationException("User is disabled.");
		}

		String salt = user.getSalt();
		String userHash = user.getPasswordHash();
		if (salt == null || userHash == null) {
			throw new AuthenticationException("User was not created properly.");
		}

		byte[] bSalt = Base64.decodeBase64(salt);

		try {
			String proposedHash = CryptoUtil.getPasswordHash(password, bSalt);
			if (!userHash.equals(proposedHash)) {
				throw new AuthenticationException("Password did not match.");
			}
		} catch (GeneralSecurityException e) {
			throw new AuthenticationException("Failed to hash password.");
		}
	}

	@Override
	public void actionAllowed(String username, Actions action)
			throws AuthorizationException {
		// TODO Auto-generated method stub

	}

	@Override
	public User selectUserByEmail(String email) {
		return this.usersMapper.selectUserByEmail(email);
	}

	@Override
	public int updateUsersSessionKey(byte[] sessionKey, String email) {
		return this.usersMapper.updateUsersSessionKey(sessionKey, email);
	}

	@Override
	public int createUser(String username, String password)
			throws GeneralSecurityException, IOException {
		Assert.hasText(password, "Password is not provided.");
		Assert.hasText(username, "Username is not provided.");

		// TODO Should we set a max length for username

		byte[] bSalt = CryptoUtil.generateSalt();
		String passwordHash = CryptoUtil.getPasswordHash(password, bSalt);

		String directory = new BigInteger(50, random).toString(32);
		
		User user = new User();
		user.setEmail(username);
		user.setPasswordHash(passwordHash);
		user.setDisabled(false);
		user.setDirectory(directory);
		user.setSalt(Base64.encodeBase64String(bSalt));

		int result = this.usersMapper.insertUser(user);
		
		if(result == 1) { 
			SecretKey secretKey = CryptoUtil.genUserSessionKey();
			CryptoUtil.saveKeyStore(secretKey, directory, password);
		}
		
		return result;
	}

	@Override
	public int resetPassword(String username, String newPassword, String oldPassword)
			throws AuthenticationException, GeneralSecurityException, IOException {
		Assert.hasText(username, "Username is not provided.");
		Assert.hasText(newPassword, "New password is not provided.");

		byte[] bSalt = CryptoUtil.generateSalt();
		String newPasswordHash = CryptoUtil.getPasswordHash(newPassword, bSalt);

		int result = this.usersMapper.updateUserPassword(username, newPasswordHash,
				Base64.encodeBase64String(bSalt));
		
		if(result == 1) {
			User user = this.usersMapper.selectUserByEmail(username);
			// Replace the key store to use the new password
			CryptoUtil.replaceKeyStore(user.getDirectory(), newPassword, oldPassword);
		}
		
		return result;
	}

	@Override
	public int disableUser(String username) {
		Assert.hasText(username, "Username is not provided.");

		return this.usersMapper.disableUser(username);
	}

	@Override
	public int enableUser(String username) {
		Assert.hasText(username, "Username is not provided.");

		return this.usersMapper.enableUser(username);
	}

	@Override
	public int updateUserEmail(String oldEmail, String newEmail) {

		Assert.hasText(oldEmail, "Old email address is not provided.");
		Assert.hasText(newEmail, "New email address is not provided.");

		return this.usersMapper.updateUserEmail(oldEmail, newEmail);
	}

	@Override
	public int removeUser(String username) {
		Assert.hasText(username, "Username is not provided.");
		//TODO: remove foreign keys?
		
		return this.usersMapper.removeUser(username);
	}

}